import { Injectable } from '@angular/core';
import { Observable, of } from 'rxjs';
import {HttpClient,HttpHeaders} from "@angular/common/http";
import {catchError,map,tap} from "rxjs/operators";

import { Hero } from './hero';
// import { HEROES} from './mock-heroes';
import { MessageService} from './message.service';

/**
 * 在要求 Angular 把 HeroService 注入到 HeroesComponent 之前，
 * 你必须先把这个服务提供给依赖注入系统。
 * 通过给 @Injectable 装饰器添加元数据的形式，
 * 为该服务把提供商注册到根注入器上,就会发现 providedIn 元数据的值是 'root'：
 *
 * 当你在顶层提供该服务时，
 * Angular 就会为 HeroService 创建一个单一的、共享的实例，
 * 并把它注入到任何想要它的类上。
 */
@Injectable({
  providedIn: 'root'
})

export class HeroService {
  private heroesUrl = 'api/heroes';
  httpOptions = {
    headers: new HttpHeaders({'Content-Type': 'application/json'})
  };

  /**
   * 这是一个典型的“服务中的服务”场景： 你把 MessageService 注入到了 HeroService 中
   * @param messageService
   */
  constructor(
    private http: HttpClient,
    private messageService: MessageService
  ) { }

  getHeroes(): Observable<Hero[]> {
    // this.messageService.add('HeroService: fetched heroes');
    // this.log('fetched heroes');
    // return of(HEROES);
    return this.http.get<Hero[]>(this.heroesUrl).pipe(tap(heroes => this.log('fetched heroes')),catchError(this.handleError('getHeroes',[])));
  }

  // getHero(id: number): Observable<Hero> {
  //   // this.messageService.add(`HeroService: fetched hero id=${id}`);
  //   this.log(`fetched hero id=${id}`);
  //   return of(HEROES.find(hero => hero.id === id));
  // }

  /** GET hero by id. Will 404 if id not found */
  getHero(id: number): Observable<Hero> {
    const url = `${this.heroesUrl}/${id}`;
    return this.http.get<Hero>(url).pipe(
      tap(_ => this.log(`fetched hero id=${id}`)),
      catchError(this.handleError<Hero>(`getHero id=${id}`))
    );
  }

  updateHero(hero: Hero): Observable<any> {
    return this.http.put(this.heroesUrl,hero,this.httpOptions).pipe(
      tap(_=> this.log(`updated hero id=${hero.id}`)),
      catchError(this.handleError<any>('updateHero'))
    );
  }
  /**
   * 它调用 HttpClient.post() 而不是 put()。
   * @param hero
   */
  addHero(hero: Hero): Observable<Hero> {
    return this.http.post<Hero>(this.heroesUrl, hero, this.httpOptions).pipe(
      tap((hero: Hero) => this.log(`added hero w/ id=${hero.id}`)),
      catchError(this.handleError<Hero>('addHero'))
    );
  }

  /**
   * 它调用了 HttpClient.delete。
   * URL 就是英雄的资源 URL 加上要删除的英雄的 id。
   * 你不用像 put 和 post 中那样发送任何数据。
   * 你仍要发送 httpOptions。
   */
  /** DELETE: delete the hero from the server */
  deleteHero (hero: Hero | number): Observable<Hero> {
    const id = typeof hero === 'number' ? hero : hero.id;
    const url = `${this.heroesUrl}/${id}`;

    return this.http.delete<Hero>(url, this.httpOptions).pipe(
      tap(_ => this.log(`deleted hero id=${id}`)),
      catchError(this.handleError<Hero>('deleteHero'))
    );
  }

  /**
   * 如果没有搜索词，该方法立即返回一个空数组。
   * 剩下的部分和 getHeroes() 很像。
   * 唯一的不同点是 URL，它包含了一个由搜索词组成的查询字符串。
   */
  /* GET heroes whose name contains search term */
  searchHeroes(term: string): Observable<Hero[]> {
    if (!term.trim()) {
      // if not search term, return empty hero array.
      return of([]);
    }
    return this.http.get<Hero[]>(`${this.heroesUrl}/?name=${term}`).pipe(
      tap(_ => this.log(`found heroes matching "${term}"`)),
      catchError(this.handleError<Hero[]>('searchHeroes', []))
    );
  }

  private log(message: string) {
    this.messageService.add(`HeroService: ${message}`);
  }

  /**
   * Handle Http operation that failed.
   * Let the app continue.
   * @param operation - name of the operation that failed
   * @param result - optional value to return as the observable result
   */
  private handleError<T> (operation = 'operation', result?: T) {
    return (error: any): Observable<T> => {

      // TODO: send the error to remote logging infrastructure
      console.error(error); // log to console instead

      // TODO: better job of transforming error for user consumption
      this.log(`${operation} failed: ${error.message}`);

      // Let the app keep running by returning an empty result.
      return of(result as T);
    };
  }
}
